package org.fujene.structure;

import java.io.Serializable;

/** This is the detailed class to represent pointer to any items in memory block.
 * values are consist of cluster, block, point, Term frequency, as following value range.
 * <table>
 * <tr>
 * <th>Name</th>
 * <th>Value range</th>
 * <th>Hold value when serialize</th>
 * </tr>
 * <tr>
 * <td>cluster</td>
 * <td>0~{@link Integer.MAX_VALUE}</td>
 * <td>×</td>
 * </tr>
 * <tr>
 * <td>block</td>
 * <td>0~16777214, -1 means nothing</td>
 * <td>○</td>
 * </tr>
 * <tr>
 * <td>point</td>
 * <td>0~16777214, -1 means nothing</td>
 * <td>○</td>
 * </tr>
 * <tr>
 * <td>termfreq</td>
 * <td>0~65535</td>
 * <td>△</td>
 * </tr>
 * </table>
 * 
 * @author iamyusk */
public final class LookupPointer implements Comparable<LookupPointer>, Serializable{
	private static final long serialVersionUID = -4610840192134163607L;

	private int cluster;
	private int block;
	private int point;
	private int termfreq;

	/** It represents (-1, -1), with termfreq: -1. */
	public static final LookupPointer EMPTY = new LookupPointer();
	/** It represents (-128, -1), with termfreq: -1. */
	public static final LookupPointer EMPTY_RELATIVE = new LookupPointer(-128, -1);

	public static final int BLOCK = 3;
	public static final int SHORTBLOCK = 1;
	public static final int POINT = 3;
	public static final int TERMFREQ = 2;
	public static final int PTRLONG = BLOCK + POINT + TERMFREQ;
	public static final int PTR = BLOCK + POINT;
	public static final int PTRSHORT = SHORTBLOCK + POINT;

	public LookupPointer(){
		this.cluster = -1;
		this.block = -1;
		this.point = -1;
		this.termfreq = -1;
	}

	// copy constructor
	public LookupPointer(LookupPointer ptr){
		this.block = ptr.block;
		this.point = ptr.point;
		this.termfreq = ptr.termfreq;
	}

	private LookupPointer(int block, int point){
		this(block, point, -1);
	}

	private LookupPointer(int block, int point, int termfreq){
		if((block < -2 && block != -128) || point < -1)
			throw new IllegalArgumentException("Block: " + block + " Point: " + point + " underflow.");
		if(block > 16777214 || point > 16777214)
			throw new IllegalArgumentException("Block: " + block + " Point: " + point + " out of range.");
		this.block = block;
		this.point = point;
		this.termfreq = termfreq;
	}

	public static LookupPointer valueOf(int block, int point){
		return new LookupPointer(block, point);
	}

	public static LookupPointer valueOf(int block, int point, int termfreq){
		return new LookupPointer(block, point, termfreq);
	}

	public LookupPointer withCluster(int cluster){
		this.cluster = cluster;
		return this;
	}

	public int cluster(){
		return cluster;
	}

	public LookupPointer modify(int block, int point){
		this.block = block;
		this.point = point;
		return this;
	}

	public void incrementPoint(int count){
		this.point += count;
	}

	public int block(){
		return block;
	}

	public int point(){
		return point;
	}

	public int freq(){
		return termfreq;
	}

	public String toString(){
		return String.format("(%d, %d)", this.block, this.point);
	}

	public String toStringEx(){
		return String.format("(%d, %d) - freq: %d", this.block, this.point, this.termfreq);
	}

	public boolean equals(LookupPointer q){
		if(q.block == this.block && q.point == this.point) return true;
		return false;
	}

	public static LookupPointer fromBytes(byte[] b){
		return fromBytes(b, 0);
	}

	public static LookupPointer fromBytes(byte[] b, int from){
		if(from + PTR > b.length)
			throw new IndexOutOfBoundsException("Cannot convert byte array to pointer, length: " + b.length
				+ " from: " + from);
		LookupPointer newPtr = new LookupPointer();

		newPtr.block = byteToInt(b, from, BLOCK);
		newPtr.point = byteToInt(b, from + BLOCK, POINT);
		newPtr.termfreq = -1;

		return newPtr;
	}

	public static LookupPointer fromBytesShort(byte[] b){
		return fromBytesShort(b, 0);
	}

	public static LookupPointer fromBytesShort(byte[] b, int from){
		if(from + PTRSHORT > b.length)
			throw new IndexOutOfBoundsException("Cannot convert byte array to pointer, length: " + b.length
				+ " from: " + from);
		LookupPointer newPtr = new LookupPointer();

		newPtr.block = byteToInt(b, from, SHORTBLOCK);
		newPtr.point = byteToInt(b, from + SHORTBLOCK, POINT);
		newPtr.termfreq = -1;

		return newPtr;
	}

	public static LookupPointer fromBytesLong(byte[] b){
		return fromBytesLong(b, 0);
	}

	public static LookupPointer fromBytesLong(byte[] b, int from){
		if(from + PTRLONG > b.length)
			throw new IndexOutOfBoundsException("Cannot convert byte array to pointer, length: " + b.length
				+ " from: " + from);
		LookupPointer newPtr = new LookupPointer();

		newPtr.block = byteToInt(b, from, BLOCK);
		newPtr.point = byteToInt(b, from + BLOCK, POINT);
		newPtr.termfreq = byteToInt(b, from + BLOCK + POINT, TERMFREQ);

		return newPtr;
	}

	public byte[] toBytes(){
		byte[] b = new byte[PTR];
		int valtmp;
		byte temp = 0;

		valtmp = this.block;
		for(int i = BLOCK - 1; i >= 0; i--){
			temp = (byte)(valtmp & 0xFF);
			b[i] = temp;
			valtmp >>= 8;
		}

		valtmp = this.point;
		for(int i = BLOCK + POINT - 1; i >= BLOCK; i--){
			temp = (byte)(valtmp & 0xFF);
			b[i] = temp;
			valtmp >>= 8;
		}

		return b;
	}

	public byte[] toBytesShort(){
		byte[] b = new byte[PTRSHORT];
		int valtmp;
		byte temp = 0;

		valtmp = this.block;
		for(int i = SHORTBLOCK - 1; i >= 0; i--){
			temp = (byte)(valtmp & 0xFF);
			b[i] = temp;
			valtmp >>= 8;
		}

		valtmp = this.point;
		for(int i = SHORTBLOCK + POINT - 1; i >= SHORTBLOCK; i--){
			temp = (byte)(valtmp & 0xFF);
			b[i] = temp;
			valtmp >>= 8;
		}

		return b;
	}

	public byte[] toBytesLong(){
		byte[] b = new byte[PTRLONG];
		int valtmp;
		byte temp = 0;

		valtmp = this.block;
		for(int i = BLOCK - 1; i >= 0; i--){
			temp = (byte)(valtmp & 0xFF);
			b[i] = temp;
			valtmp >>= 8;
		}

		valtmp = this.point;
		for(int i = BLOCK + POINT - 1; i >= BLOCK; i--){
			temp = (byte)(valtmp & 0xFF);
			b[i] = temp;
			valtmp >>= 8;
		}

		valtmp = this.termfreq;
		for(int i = BLOCK + POINT + TERMFREQ - 1; i >= BLOCK + POINT; i--){
			temp = (byte)(valtmp & 0xFF);
			b[i] = temp;
			valtmp >>= 8;
		}

		return b;
	}

	private static int byteToInt(byte[] b, int from, int length){
		int temp = 0;
		if(length > 4 || length <= 0) length = 4;

		for(int i = from; i < from + length; i++){
			// Big endian
			temp <<= 8;
			temp += (b[i] & 0xff);
		}

		// get complement
		if(temp >= 0 && (temp == (1 << (length * 8)) - 1)){
			temp -= (1 << (length * 8));
		}

		return temp;
	}

	@Override
	public boolean equals(Object obj){
		if(obj instanceof LookupPointer) return equals((LookupPointer)obj);
		else return false;
	}

	@Override
	public int compareTo(LookupPointer query){
		if(query.block != this.block){
			return this.block - query.block;
		}else{
			if(query.point != this.point){
				return this.point - query.point;
			}else{
				return 0;
			}
		}
	}

	@Override
	public int hashCode(){
		return((this.block << 16) + this.point);
	}

	@Override
	protected Object clone() throws CloneNotSupportedException{
		return new LookupPointer(this.block, this.point, this.termfreq);
	}
}
